/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ca.trader.regions;

import ca.trader.logger.TraderLogger;
import ca.trader.utils.TraderTreeSet;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Iterator;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * This class contains the mappings/values for all the countries, regions and cities in the world.  The cities supported are those that have 
 * a population of greater than 50,000.
 * An instance of this class can only be initiated via the static <i>fromXMLFile</i> method.
 * The <i>fromXMLFile</i> method will parse the XML file that is passed to it.
 * It will create a RegionManager with the country/region/city data in that XML file.
 * A sample XML file is as follows:
 * <pre>
 * {@code
 * <CountryList>
	  <Country>
		  <Name>Canada</Name>
		  <Region>
			  <Name>British Columbia</Name>
			  <Cities>Abbotsford,Chilliwack,Kamloops,Kelowna,Nanaimo,Prince George,Vancouver,Victoria,White Rock</Cities>
		  </Region>
		  <Region>
			  <Name>Manitoba</Name>
			  <Cities>Winnipeg</Cities>
		  </Region>
			* 
	  </Country>
	  <Country>
		  <Name>Saint Helena</Name>
		  <Region>
			  <Name>Saint Helena</Name>
			  <Cities>Other</Cities>
		  </Region>
		  <Region>
			  <Name>Tristan da Cunha</Name>
			  <Cities>Other</Cities>
		  </Region>
		  <Region>
			  <Name>Ascension</Name>
			  <Cities>Other</Cities>
		  </Region>
		  </Country>
  </CountryList>
 * }
 * </pre>
 * The CountryList node must contain a set of "Country" nodes.
 * Each "Country" node must have a "Name" node, specifying the name of the country.
 * Each "Country" node must have a list of "Region" nodes.
 * Each "Region" node must have a "Name" node, specifying the name of the region.
 * Each "Region" node must have a "Cities" node, which is a comma separated list of the cities in the region.
 * @author Ahsan
 */
/* RegionManager reads the list from xml file and put them in list
 and the list and make it available for other classes */
public class RegionManager implements Iterable<Country>
{
	 private static final String _className = MapRegionToCities.class.getName();

	 // Name of the tag for describing a list of country
	 private static final String COUNTRY_LIST_TAG = "CountryList";
	 
	 // Name of the tag for describing a country
	 private static final String COUNTRY_TAG = "Country";
	 
	 // Name of the tag for describing a region
	 private static final String REGION_TAG = "Region";
	 
	 // Name of the tag for describing the name of a country
	 private static final String COUNTRY_NAME_TAG = "Name";
	 
	 // Name of the tag for describing the name of a region
	 private static final String REGION_NAME_TAG = "Name";
	 
	 // Name of the tag for describing the cities in a region
	 private static final String REGION_CITIES_TAG = "Cities";
	 
	 // A list of all the countries in this RegionManager
	 private TraderTreeSet<Country> countryList = new TraderTreeSet<Country>();

	 /* RegionManager constructor */
	 private RegionManager()
	 {
	 }
	 	

	 /**
	  * Gets the value of the tag <i>tagName</i> in the node <i>node</i> located at index <i>index</i> in the node
	  * @param node the node to look up
	  * @param tagName the name of the tag to look up
	  * @param index which index to look up
	  * @return the value of the tag
	  */
	 private static String getTagValue(Node node, String tagName, int index)
	 {
		  Element element = (Element) node;

		  NodeList country = element.getElementsByTagName(tagName);

		  Element nameElement = (Element) country.item(index);

		  NodeList textList = nameElement.getChildNodes();

		  return ((Node) textList.item(0)).getNodeValue().trim();
	 }
	 
	 /**
	  * Gets the value of the tag <i>tagName</i> in the node <i>node</i> located at index 0
	  * @param node the node to look up
	  * @param tagName the name of the tag to look up
	  * @return the value of the tag
	  */
	 private static String getTagValue(Node element, String tagName)
	 {
		  return getTagValue(element, tagName, 0);
	 }

	 /**
	  * Parse an xml file with the convention as follows (as an example>:
	  * <pre>
	  * {@code
	  * <CountryList>
		  <Country>
			  <Name>Canada</Name>
			  <Region>
				  <Name>British Columbia</Name>
				  <Cities>Abbotsford,Chilliwack,Kamloops,Kelowna,Nanaimo,Prince George,Vancouver,Victoria,White Rock</Cities>
			  </Region>
			  <Region>
				  <Name>Manitoba</Name>
				  <Cities>Winnipeg</Cities>
			  </Region>
				* 
		  </Country>
		  <Country>
			  <Name>Saint Helena</Name>
			  <Region>
				  <Name>Saint Helena</Name>
				  <Cities>Other</Cities>
			  </Region>
			  <Region>
				  <Name>Tristan da Cunha</Name>
				  <Cities>Other</Cities>
			  </Region>
			  <Region>
				  <Name>Ascension</Name>
				  <Cities>Other</Cities>
			  </Region>
			  </Country>
	  </CountryList>
	  * }
	  * </pre>
	  * The CountryList node must contain a set of "Country" nodes.
	  * Each "Country" node must have a "Name" node, specifying the name of the country.
	  * Each "Country" node must have a list of "Region" nodes.
	  * Each "Region" node must have a "Name" node, specifying the name of the region.
	  * Each "Region" node must have a "Cities" node, which is a comma separated list of the cities in the region.
	  * @param xmlFilePath the path of the XML file to parse
	  * @return the RegionManager parsed
	  * @throws SAXException if there an exception in parsing the xml file
	  * @throws IOException if there was an exception in reading the xml file
	  * @throws ParserConfigurationException if there an exception in parsing the xml file
	  */
	 public static RegionManager fromXMLFile(String xmlFilePath) throws SAXException, IOException, ParserConfigurationException
	 {
		  String _methodName = "fromFile";
		  
		  // CReate the region manager object
		  RegionManager regionManager = new RegionManager();

		  TraderLogger.logMethodEntry(_className, _methodName);

		  // Create the input source to read from the xml file
		  InputSource is = new InputSource(new InputStreamReader(new FileInputStream(xmlFilePath), "UTF-8"));
		  is.setEncoding("UTF-8");

		  TraderLogger.logInfo(_className, _methodName, "Starting parsing Region XML file: " + xmlFilePath);

		  // Parse the XML file
		  Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
		  doc.getDocumentElement().normalize();

		  // Get all the Country nodes
		  NodeList listOfCountries = doc.getElementsByTagName(COUNTRY_TAG);

		  int totalCountries = listOfCountries.getLength();

		  // Iterate through all the country nodes
		  for (int s = 0; s < totalCountries; s++)
		  {
			   // Get the current country node
			   Node countryNode = listOfCountries.item(s);

			   // Make sure it's an element node
			   if (countryNode.getNodeType() == Node.ELEMENT_NODE)
			   {
				    // Get the name of the country
				    String countryName = getTagValue(countryNode, COUNTRY_NAME_TAG);

				    // Create the country object
				    Country country = new Country(countryName);

				    //LoggerManager.logInfo(_className, _methodName, "Found country: " + countryName);

				    // Convert the country node into an element
				    Element countryElement = (Element) countryNode;

				    // Get all the region nodes inside the current country node
				    NodeList regionList = countryElement.getElementsByTagName(REGION_TAG);

				    int totalRegions = regionList.getLength();

				    // Iterate through each region node in the current country node
				    for (int b = 0; b < totalRegions; b++)
				    {
						// Get the current region node
						Node regionNode = regionList.item(b);
						
						// Make sure it's an element node
						if (regionNode.getNodeType() == Node.ELEMENT_NODE)
						{
							 // Get the name of the region
							 String regionName = getTagValue(regionNode, REGION_NAME_TAG);

							 // Initiate the region object
							 Region region = new Region(regionName);

							 //LoggerManager.logInfo(_className, _methodName, "Found region: " + regionName);

							 // Get all the cities in the region, put them into an array by splitting by ","
							 String cities[] = getTagValue(regionNode, REGION_CITIES_TAG).split(",");

							 // Add all the cities to the region
							 region.addCities(cities);

							 //LoggerManager.logInfo(_className, _methodName, "Found cities: " + StringUtils.join(region.getCityList(), ","));

							 //Add the Region to the country
							 country.addRegion(region);

						}
						else
						{
							 TraderLogger.logInfo(_className, _methodName, "Did not find " + REGION_TAG + " tag as an element node");
						}
				    }

				    // Add the region to the region manager
				    regionManager.addCountry(country);

			   }
			   else
			   {
				    TraderLogger.logInfo(_className, _methodName, "Did not find " + COUNTRY_TAG + " tag as an element node");
			   }
		  }
		  TraderLogger.logMethodExit(_className, _methodName);
		  

		  return regionManager;
	 }

	 /**
	  * Add the country to the current region manager
	  * @param country the country to add
	  */
	 public void addCountry(Country country)
	 {
		  countryList.add(country);
	 }

	 /**
	  * Add a list of countries to the current region manager
	  * @param countries the countries to add
	  */
	 public void addCountries(Country[] countries)
	 {
		  for (Country country : countries)
		  {
			   addCountry(country);
		  }
	 }

	 /**
	  * Get the country with the name <i>name</i>
	  * @param name
	  * @return the region with the name <i>name</i>
	  */
	 public Country getCountry(String name)
	 {
		  return countryList.findIdenticalItem(new Country(name));
	 }

	 /**
	  * Returns true if this RegionManager object contains the country <i>country</i>
	  * @param country the country to search
	  * @return true if this RegionManager contains the country <i>country</i>
	  */
	 public boolean containsCountry(Country country)
	 {
		  return countryList.contains(country);
	 }
	 
	 public boolean containsCountry (String name)
	 {
		  return containsCountry (new Country (name));
	 }

	 /**
	  * Returns an iterator over all the countries in this RegionManager
	  * @return an iterator over all the countries in this RegionManager
	  */
	 @Override
	 public Iterator<Country> iterator()
	 {
		  return countryList.iterator();
	 }

	 /**
	  * A list of all the countries in this RegionManager
	  * @return A list of all the countries in this RegionManager
	  */
	 public Country[] getCountryList()
	 {
		  return countryList.toArray(new Country[0]);
	 }

}
